/**************************************************************************

Copyright:HUST AIA

Author:Foxwen

Date:2025-2-24

Description: 基于Xilinx Mpsoc 的ADCDMA驱动的核心框架代码，包括设备注册、IOCTL处理

**************************************************************************/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h> 
#include <linux/cdev.h> 
#include <linux/device.h>
#include <linux/dmaengine.h>
#include <linux/dma-mapping.h>
#include <linux/platform_device.h>
#include <linux/of_dma.h>
#include <linux/scatterlist.h>
#include <linux/uaccess.h>
#include <adcdma_core.h>

static ADC2DMA_DEVICE_MESG *ada_dev_mesg
static ADC2DMA_DRIVER_MESG *ada_dri_mesg;

/*根据ioctl设置DMA缓冲区*/
static int set_dma_buf(unsigned int buf_num)
{
    int ret = 0;
    for(int i = 0; i < buf_num; i ++)
    {
        //order为分配的页数，3表示分配2^3页，返回虚拟地址,分配的物理地址连续
        //返回到虚拟地址属于内核空间的直接映射区，物理地址与虚拟地址之间只存在线性偏移
        ada_dri_mesg->dma_buf[i] = (char *)__get_free_pages(GFP_KERNEL, get_order(DMA_BUF_SIZE));
        if (!ada_dri_mesg->dma_buf[i])
        {
            printk("Failed to allocate memory for DMA buffers\n");
            //回滚已分配的物理页
            while(-- i >= 0){
                free_pages((unsigned long)ada_dri_mesg->dma_buf[i], get_order(DMA_BUF_SIZE));
            }
            return -ENOMEM;
        }
        //将虚拟地址转换为设备可访问的地址
        ada_dri_mesg->dma_dsts[i] = dma_map_single(ada_dri_mesg->dev,ada_dri_mesg->dma_buf[i], DMA_BUF_SIZE, DMA_FROM_DEVICE);
    }
    return 0;
}
/*根据ioctl开启流*/
static int dma_start_stream()
{
    enum dma_ctrl_flags flags = DMA_PREP_INTERRUPT;
    //从低0个缓冲区开始传输
    ada_dri_mesg -> head = 0;
    ada_dri_mesg -> tail = 0;
    ada_dri_mesg -> desc = dmaengine_prep_slave_single(ada_dri_mesg->dma_ch, ada_dri_mesg->dma_dsts[ada_dri_mesg->head], DMA_BUF_SIZE, DMA_DEV_TO_MEM, flags);
	ada_dri_mesg -> cookie = dmaengine_submit(ada_dri_mesg -> desc);
	/*DMA中断回调*/
	ada_dri_mesg -> desc -> callback = dma_callback;
	//desc->callback_param = (void*)callback_data;

    dma_async_issue_pending(ada_dri_mesg->dma_ch);
    return 0;
}


/*
 * 系统调用接口
 */
 static int dma_open(struct inode* inode, struct file* filp)
{
	printk("-KernelPrint dma_open-\n");
	return 0;
}
static ssize_t dma_read(struct file* filp, char __user* buf, size_t count, loff_t* fops)
{
	printk("-KernelPrint dma_read-\n");
	return 0;
}
static ssize_t dma_write(struct file* filp, const char __user* buf, size_t count, loff_t* fops) 
{
    printk("-KernelPrint dma_write-\n");
	return 0;
}
int dma_release(struct inode* inode, struct file* file)
{
	printk("-KernelPrint dma_release-\n");
	return 0;
}


/*
 * 实现ioctl
 */
long dma_drv_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    int ret = 0;
	switch (cmd) {
	case SET_DMA_BUF_NUM:
        unsigned int buf_num;
        if (copy_from_user(&buf_num, (void __user *)arg, sizeof(buf_num))) {
            return -EFAULT;  // 用户指针无效
        }
        if(buf_num == 0 || buf_num > MAX_DMA_BUF_CNT)
            return -1;
        ada_dri_mesg->dma_buf_num = buf_num;
        ret = set_dma_buf(ada_dri_mesg -> dma_buf_num);
        if(ret != 0) return -2;
		break;

    //开启DMA传输
    case DMA_STREAM_ON:
        if(ada_dri_mesg->dma_buf_num <= 0){
            printk("Please set DMA_BUF firstly\n");
            return -1;
        }
        ret = dma_start_stream();
        if(ret != 0) return -1;
        break;

    //更新DMA环形缓冲区用户空间读指针
    case DMA_UPDATE_TAIL:
        if(ada_dri_mesg -> tail == ada_dri_mesg -> head)
            return -1;
        ada_dri_mesg -> tail = (ada_dri_mesg -> tail + 1) % ada_dri_mesg -> buf_num;
        break;
    
	default:
		return -3;
	}
	return 0;
}

/*
 * 实现mmap函数
 * 实现DMA缓冲区到用户空间的映射
 */
 static int dma_mmap(struct file *file, struct vm_area_struct *vma)
 {
    //vm_end和vm_start由用户空间通过mmap传入,即映射起始地址（用户空间）与映射长度
    unsigned long length = vma->vm_end - vma->vm_start;
     
    if(length > DMA_BUF_SIZE)
        return -1;
    if(vma->vm_pgoff >= ada_dri_mesg -> dma_buf_num)
        return -2;
     // 将物理地址映射到用户空间
     // 左移PAGE_SHIFT位，得到物理页号（要求物理地址页对齐）
     // 根据rec_buf1与rec_buf2的物理页号，分别映射到用户空间
     // vma->vm_pgoff为用户空间传入的偏移量offset,注意只是逻辑偏移，用于判断映射的是哪个缓冲区
     if (remap_pfn_range(vma,
        vma->vm_start,
        virt_to_phys(ada_dri_mesg -> dma_buf[(unsigned int) vma->vm_pgoff]) >> PAGE_SHIFT,
        length,
        vma->vm_page_prot))
    return -3;

    return 0;
 }

const struct file_operations dma_fops = {
	.owner = THIS_MODULE,
	.open = dma_open,
	.read = dma_read,
	.write = dma_write,
	.release = dma_release,
	.ioctl = dma_drv_ioctl,
	.mmap = dma_mmap,
};


/*
 * 实现probe函数
 * 在设备树match成功后调用
 * 在/dev目录下创建设备节点，注册字符设备驱动
 * 完成DMA传输的初始化
 * 向内核申请DMA通道，采用流式DMA；通道名称必须与设备树节点中dma-names属性相同，因为dmaengine会对名称进行校验。
 */

 static int dma_pdrv_probe(struct platform_device* pdev)
 {
     int ret = 0;	
 
     ada_dev_mesg = kzalloc(sizeof(struct ADC2DMA_DEVICE_MESG), GFP_KERNEL);
     if (!ada_dev_mesg) {
         printk("Failed to allocate memory for ADC2DMA_DEVICE_MESG\n");
         return -ENOMEM;
     }
 
     ada_dri_mesg = kzalloc(sizeof(struct ADC2DMA_DRIVER_MESG), GFP_KERNEL);
     if (!ada_dri_mesg) {
         printk("Failed to allocate memory for ADC2DMA_DRIVER_MESG\n");
         return -ENOMEM;
     }
 
     
     ada_dri_mesg->dev = &pdev->dev;

     /*注册字符设备*/
     //申请设备号
     ret = alloc_chrdev_region(&ada_dev_mesg->devid, 0, 1, DEVICE_NAME);
     if (ret != 0)
     {
         printk("-KernelPrint alloc devid error-\n");
         kfree(ada_dev_mesg); 
         kfree(ada_dri_mesg);
         return -1;
     }

    //在内核注册设备
     cdev_init(&ada_dev_mesg->cdev, &dma_fops);
     ret = cdev_add(&ada_dev_mesg->cdev, ada_dev_mesg->devid, 1);
     if (ret < 0)
     {
         printk("-KernelPrint add cdev error-\n");
         kfree(ada_dev_mesg); 
         kfree(ada_dri_mesg);
         return -1;
     }

    //在文件系统注册设备
    ada_dev_mesg->class = class_create(THIS_MODULE, CLASS_NAME);
    ada_dev_mesg->device = device_create(ada_dev_mesg->class,NULL, ada_dev_mesg->devid,NULL, DEVICE_NAME);
 
     /*为驱动申请DMA通道，通道名必须与设备树节点相同*/
     ada_dri_mesg -> dma_ch = dma_request_chan(ada_dri_mesg -> dev, "dmas");//name用于指定通道，而非命名通道
     if (IS_ERR_OR_NULL(ada_dri_mesg -> dma_ch))
     {
         printk("-KernelPrint dma_request_chan failed-\n");
         return PTR_ERR(ada_dri_mesg -> dma_ch);
     }
     kfree(ada_dev_mesg); 
     kfree(ada_dri_mesg);
     return ret;
 }

 /*
 * DMA的中断回调函数，处理数据并准备开始下一次传输。
 * 同步内存并重新映射，保持缓存一致性。
 * 采用双缓冲机制，先开启下一次DMA传输，再通知用户空间处理DMA数据。
 */
static void dma_callback(void* data)
{
	int ret = 0;
	enum dma_ctrl_flags flags = DMA_PREP_INTERRUPT;
    unsigned int next_head = ada_dri_mesg->head + 1;
    if(next_head == ada_dri_mesg -> tail) 
    {
        printk("-KernelPrint dma_ring_full-\n");
        return;
    }

	//CPU缓存同步：失效缓存区,直接从内存取数据，
    //DMA_FROM_DEVICE是失效缓存，直接从内存获取数据，但缓存不会刷新
    //DMA_TO_DEVICE是将缓存写入内存（回写）
    //DMA_BIDIRECTIONAL失效 + 回写
	dma_sync_single_for_cpu(ada_dri_mesg->dev, ada_dri_mesg->dma_dsts[ada_dri_mesg->head], DMA_BUF_SIZE, DMA_BIDIRECTIONAL);
    //更新环形缓冲区head（考虑使用位掩码）
    ada_dri_mesg->head = (ada_dri_mesg->head + 1) % ada_dri_mesg->dma_buf_num;

    /*更新传输描述符*/
	ada_dri_mesg->desc = dmaengine_prep_slave_single(ada_dri_mesg->dma_ch, ada_dri_mesg->dma_dsts[ada_dri_mesg->head], DMA_BUF_SIZE, DMA_DEV_TO_MEM, flags);
	ret = dmaengine_submit(ada_dri_mesg->desc);
    dma_async_issue_pending(ada_dri_mesg->dma_ch);
	if (ret < 0)
		printk("-KernelPrint Callback Prep Failed -\n");
}


int dma_pdrv_remove(struct platform_device* pdev)
{
	device_destroy(ada_dev_mesg->class, ada_dev_mesg->devid);
	class_destroy(ada_dev_mesg->class);
	cdev_del(&ada_dev_mesg->cdev);
	unregister_chrdev_region(ada_dev_mesg->devid, 1);
	printk("cdev remove finished!\n");


    for(int i = 0; i < dma_sync_single_for_cpu -> buf_num; i ++)
    {
        dma_unmap_single(&ada_dri_mesg->dev, ada_dri_mesg->dma_dsts[i], DMA_BUF_SIZE, DMA_FROM_DEVICE);
        free_pages((unsigned long)ada_dri_mesg->dma_buf[i], get_order(DMA_BUF_SIZE));
    }
    printk("free_pages finished!\n");
	dma_release_channel(ada_dri_mesg->dma_ch);
	printk("dma_release_channel finished!\n");

    kfree(ada_dev_mesg); 
    kfree(ada_dri_mesg);
	return 0;
}

const struct of_device_id dma_of_match_table[] = {
	{
		.compatible = "dmas_demo",
	},
	{}
};

struct platform_driver dma_drv_f = {
	.driver = {
				.name = "zynqmp_dma",
				.of_match_table = dma_of_match_table,
				},
	.probe = dma_pdrv_probe,
	.remove = dma_pdrv_remove,
};

static int __init dma_fun_init(void)
{
	return platform_driver_register(&dma_drv_f);
}

static void __exit dma_fun_exit(void)
{
	platform_driver_unregister(&dma_drv_f);
}

late_initcall(dma_fun_init);
module_exit(dma_fun_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Foxwen");
